import { ConnectAction } from '../action'
import type { Connect } from '../index'
import config from '@/config'
import { isValidSemVer } from '@/utils/version'
import { PacketStepHandler } from '../handler'
import type { Packet } from '../packet'
import { nanoid } from 'nanoid'
import {
  decryptWithAESGCM,
  decryptWithRSA,
  encryptWithAESGCM,
  encryptWithRSA,
  generateRandomKeyWithAESGCM
} from '@/utils/crypto'
import type { ActionsNext } from '@/utils/action'

/**
 * 发起连接
 */
export class InitiateConnectAction extends ConnectAction {
  constructor(connect: Connect) {
    super('initiateConnect', connect)
    // 交换信息
    this.actions.addAsyncAction('exchangeInfo', this.exchangeInfo_handler, true)
    // 交换密钥
    this.actions.addAction('exchangeKeys', this.exchangeKeys_handler)
    // 完成连接
    this.actions.addAction('complete', this.complete_handler)

    this.actions.addBranchChain('exchangeInfo', 'exchangeKeys', 'complete')

    this.actions.on('error', (err) => {
      this.logger.error(err, 'Initiate connect error')
    })
  }

  async exchangeInfo_handler() {
    const p = await this.connect.packetOnceAsync()
    if (p.isFatalError()) {
      throw new Error(p.readErrorContent() || 'Fatal error')
    }
    const info = p.decodeBody<Info>()
    if (!info) {
      this.connect.end()
      throw new Error('Unable to obtain information about the other party')
    }
    if (!isValidSemVer(info.version)) {
      this.connect.end()
      throw new Error("The other party's version number is illegal")
    }

    // TODO 这里的版本号比较应该主版本号不同抛出错误，次版本号不同发出警告
    // switch (compareVersions(config.version, info.version)) {
    //   case 1:
    //     this.connect.end()
    //     throw new Error('对方版本号高于我方版本号')
    //   case -1:
    //     this.connect.end()
    //     throw new Error('对方版本号低于我方版本号')
    // }

    this.connect.store.set('_info', info)
  }

  exchangeKeys_handler(actionsNext: ActionsNext) {
    const publicKeyWithRSA = this.connect.store.get('publicKeyWithRSA') || ''
    const privateKeyWithRSA = this.connect.store.get('privateKeyWithRSA') || ''
    let keyWithAESGCM = Buffer.from([0x00])
    let publicKeyWithRSA_remote: string

    const connectStepHandler = new PacketStepHandler(this.connect)

    // 接收公钥并发送的自己的公钥
    connectStepHandler.step((p, next) => {
      publicKeyWithRSA_remote = p.readBodyAsText() || ''
      this.connect.store.set('publicKeyWithRSA_remote', publicKeyWithRSA_remote)
      this.connect.sendText(publicKeyWithRSA)
      next()
    })
    // 接受key解密并将key使用对方的公钥加密发送回去
    connectStepHandler.step((p, next) => {
      const body = p.body
      if (!body) {
        this.logger.error('01 not body')
        this.connect.end()
        return
      }
      try {
        const key_remote = decryptWithRSA(body, privateKeyWithRSA)
        this.connect.sendBuffer(encryptWithRSA(key_remote, publicKeyWithRSA_remote))
        next()
      } catch (error) {
        this.logger.error('02 -> ' + error)
        this.connect.end()
        return
      }
    })
    // 接受AES-GCM密钥，并加密一段随机的数据
    connectStepHandler.step((p, next) => {
      const body = p.body
      if (!body) {
        this.logger.error('03 -> not body')
        this.connect.end()
        return
      }
      try {
        keyWithAESGCM = decryptWithRSA(body, privateKeyWithRSA)
        this.connect.store.set('keyWithAESGCM', keyWithAESGCM)
        const encrypted = encryptWithAESGCM(Buffer.from(nanoid()), keyWithAESGCM)
        this.connect.sendBuffer(encrypted.encryptedData, {
          tag: encrypted.tag
        })
        this.connect.openEncryptPacket(keyWithAESGCM)
        next()
      } catch (error) {
        this.logger.error('04 -> ' + error)
        this.connect.end()
        return
      }
    })

    const packetCP = (p: Packet) => connectStepHandler.handler(p)
    this.connect.on('packet', packetCP)
    connectStepHandler.on('complete', () => {
      this.connect.off('packet', packetCP)
      actionsNext()
    })
  }

  complete_handler(next: ActionsNext) {
    next()
  }
}

/**
 * 接收连接
 */
export class ReceiveConnectAction extends ConnectAction {
  constructor(connect: Connect) {
    super('receiveConnect', connect)
    // 交换信息
    this.actions.addAsyncAction('exchangeInfo', this.exchangeInfo_handler, true)
    // 交换密钥
    this.actions.addAction('exchangeKeys', this.exchangeKeys_handler)
    // 完成连接
    this.actions.addAction('complete', this.complete_handler)

    this.actions.addBranchChain('exchangeInfo', 'exchangeKeys', 'complete')

    this.actions.on('error', (err) => {
      connect.logger.error(err, 'Receive connect error')
    })
    connect.openTimeout(10000)
  }

  async exchangeInfo_handler() {
    const info: Info = {
      name: config.name,
      version: config.version
    }

    this.logger.info('send info', info)

    await this.connect.sendJson(info)
  }

  exchangeKeys_handler(next: ActionsNext) {
    const publicKeyWithRSA = this.connect.store.get('publicKeyWithRSA') || ''
    const privateKeyWithRSA = this.connect.store.get('privateKeyWithRSA') || ''
    const keyWithAESGCM = generateRandomKeyWithAESGCM()
    let publicKeyWithRSA_remote: string

    const key = nanoid()

    const packetStepHandler = new PacketStepHandler(this.connect)

    // 接受公钥，并发送加密后的key
    packetStepHandler.step((p, next) => {
      publicKeyWithRSA_remote = p.readBodyAsText() || ''
      if (!publicKeyWithRSA_remote) {
        this.logger.error('05 -> not publicKeyWithRSA_remote')
        this.connect.end()
        return
      }
      this.logger.info('receive publicKeyWithRSA_remote')
      this.connect.store.set('publicKeyWithRSA_remote', publicKeyWithRSA_remote)
      try {
        this.connect.sendBuffer(encryptWithRSA(Buffer.from(key), publicKeyWithRSA_remote))
        this.logger.info('send key')
        next()
      } catch (error) {
        this.logger.error('06 -> ' + error)
        this.connect.end()
      }
    })
    // 接受加密后的key，并验证，验证通过后发送AES-GCM密钥
    packetStepHandler.step((p, next) => {
      const body = p.body
      if (!body) {
        this.logger.error('07 -> not body')
        this.connect.end()
        return
      }
      try {
        const decrypted = decryptWithRSA(body, privateKeyWithRSA)
        if (decrypted.toString() === key) {
          this.connect.store.set('keyWithAESGCM', keyWithAESGCM)
          this.connect.sendBuffer(encryptWithRSA(keyWithAESGCM, publicKeyWithRSA_remote))
          this.logger.info('send keyWithAESGCM')
          next()
        } else {
          this.logger.error('08 -> key not equal')
          this.connect.end()
        }
      } catch (error) {
        this.logger.error('09 -> ' + error)
        this.connect.end()
        return
      }
    })
    // 接受使用AES-GCM加密的随机数据，并验证，验证通过后完成交换密钥
    packetStepHandler.step((p, next) => {
      const body = p.body
      if (!body) {
        this.logger.error('10 -> not body')
        this.connect.end()
        return
      }
      try {
        decryptWithAESGCM(body, keyWithAESGCM, p.header.tag || '')
        this.connect.openEncryptPacket(keyWithAESGCM)
        this.logger.info('complete exchangeKeys')
        next()
      } catch (error) {
        this.logger.error('11 -> ' + String(error))
        this.connect.end()
        return
      }
    })

    const packetCP = (p: Packet) => packetStepHandler.handler(p)
    this.connect.on('packet', packetCP)
    packetStepHandler.on('complete', () => {
      this.connect.off('packet', packetCP)
      next()
    })

    // 发送自己的公钥
    this.connect.sendText(publicKeyWithRSA)
  }

  complete_handler(next: () => void) {
    this.logger.info('Receive connect complete')
    next()
  }
}

type Info = {
  name: string
  version: string
}
